/*
 * @Author: Wangjun
 * @Date: 2023-09-01 13:39:49
 * @LastEditTime: 2025-06-05 17:43:39
 * @LastEditors: wangjun haodreams@163.com
 * @Description:创建默认的router
 * @FilePath: \golib\autoroute\defaulter\default.go
 * hnxr
 */
package defaulter

import (
	"fmt"
	"io"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"gitee.com/haodreams/golib/autoroute"
	"gitee.com/haodreams/golib/autoroute/controller"
	"gitee.com/haodreams/golib/minauth"
	"gitee.com/haodreams/golib/estring"
	"gitee.com/haodreams/golib/startruntime"
	"gitee.com/haodreams/libs/config"
	"gitee.com/haodreams/libs/easy"
	"gitee.com/haodreams/libs/tailsnap"
	"github.com/gin-contrib/gzip"
	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
	"golang.org/x/text/encoding/simplifiedchinese"
)

func DefaultAuth(c *gin.Context) (any, error) {
	//如果是本地发起的请求则不需要认证s
	host := c.RemoteIP()
	if host == "127.0.0.1" { //本地不做验证
		return nil, nil
	}
	return minauth.CallbackNeedAuth(c)
}

// 创建默认的路由
// 开启缓存，不能通过安全认证
func DefaultEngine(cached bool, defaultUser func() (string, string), auth func(*gin.Context) (any, error)) (router *gin.Engine) {
	gin.SetMode(config.DefaultString("gin.mode", gin.ReleaseMode))

	router = gin.New()
	//安全扫描出现的响应头缺失安全问题
	if !cached {
		router.Use(SecureHeader)
	}
	if defaultUser != nil {
		//需要认证相关的功能
		if auth == nil {
			auth = DefaultAuth
		}
		UseDefaultAuth(defaultUser, auth)
		minauth.Save()
	}
	
	ris := router.Routes()
	mapRoute := make(map[string]bool)
	for _, route := range ris {
		mapRoute[route.Path] = true
	}

	if ok := mapRoute["/"]; !ok {
		router.StaticFile("/", "static/dist/index.html")
	}
	if ok := mapRoute["/static"]; !ok {
		router.Static("/static", "static")
	}

	//router.Static("/md", "md") //存放md资源文件
	router.GET("/md/*path", func(c *gin.Context) {
		//c.File("conf/app.md") // 手动返回文件
		if auth:=controller.GetAuth() ; auth != nil { //如果没有认证， 则不返回
			_,err:=auth(c)
			if err!= nil {
				c.String(403, "403")
				return
			}
		}
		path := c.Param("path")
		if path == "" {
			c.String(404, "404")
			return
		}
		//解码
		path, err := url.QueryUnescape(path)
		if err!= nil {
			c.String(404, "404")
			return
		}
		if path == "/app.md"{
			c.File("conf/app.md")
			return
		}
		path = filepath.Join("md", path)
		os,err:=os.Stat(path)
		if err!= nil {
			c.String(404, "404")
			return	
		}
		if os.IsDir() {
			path = filepath.Join(path, "index.md")	
		}
		c.File(path)
	})
	// router.StaticFile("/md/app.md", "conf/app.md")
	router.StaticFile("/favicon.ico", "static/favicon.ico")

	if config.DefaultBool("pprof.enable", false) {
		pprof.Register(router)
	}
	router.Use(gin.Recovery())
	//开启压缩
	router.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPathsRegexs([]string{".*"})))
	router.GET("/sys/starttime", func(c *gin.Context) { //获取运行时间
		c.String(200, easy.FormatTime(startruntime.GetStartRunTime()))
	})
	router.GET("/sys/status", func(c *gin.Context) { //获取服务器当前时间
		c.String(200, easy.Now())
	})
	//获取加密字符串
	router.GET("/sys/encrypt", func(c *gin.Context) {
		html := `<!DOCTYPE html>
		<html> <head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>数据加密</title>
		</head><body><form method="post">
		数据加密:<input type="input" name="key" value="" /> 	<input type="submit" name="submit" />
		</form> </body></html>
		`
		c.Writer.Header().Add("Content-Type", "text/html")
		c.Writer.WriteHeader(200)
		c.Writer.Write([]byte(html))
	})
	//加密字符串
	router.POST("/sys/encrypt", func(c *gin.Context) {
		c.Request.ParseForm()
		keys := c.Request.PostForm["key"]
		if len(keys) > 0 {
			keys[0] = strings.TrimSpace(keys[0])
			if keys[0] != "" {
				c.String(200, config.EncodeValue(keys[0]))
			}
			return
		}
		c.String(200, "参数错误")
	})

	router.GET("/sys/log/tail", TailLog)
	router.GET("/sys/log/taillog", Taillog)

	//上传文件到临时目录
	router.POST("/sys/file/upload", func(m *gin.Context) {
		header, err := m.FormFile("file")
		if err != nil {
			m.JSON(200, map[string]any{"code": 412, "msg": err.Error()})
			return
		}
		dst := header.Filename
		fileName := filepath.Base(dst)
		f, err := header.Open()
		if err != nil {
			m.JSON(200, map[string]any{"code": 500, "msg": err.Error()})
			return
		}

		defer f.Close()

		if _, err := os.Stat("temp"); err != nil {
			os.MkdirAll("temp", 0755)
		}

		lf, err := os.Create("temp/" + fileName)
		if err != nil {
			m.JSON(200, map[string]any{"code": 500, "msg": err.Error()})
			return
		}

		defer lf.Close()

		size, _ := io.Copy(lf, f)
		msg := estring.FormatSize(size)
		m.JSON(200, map[string]any{"code": 200, "msg": "上传成功， 上传大小:" + msg})
	})
	router.GET("/sys/file/upload", func(c *gin.Context) {
		html := `<!DOCTYPE html>
		<html> <head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>upload file</title>
		</head><body><form action="/sys/file/upload" method="post" enctype="multipart/form-data">
		<input type="file" name="file" value="" /> 	<input type="submit" name="submit" />
		</form> </body></html>
		`
		c.Writer.Header().Add("Content-Type", "text/html")
		c.Writer.WriteHeader(200)
		c.Writer.Write([]byte(html))
	})

	//多个模板文件
	//router.LoadHTMLGlob("views/**/*")
	autoroute.Normal(router, "/v1/log/*path", &LogController{})
	autoroute.Normal(router, "/v1/login/*path", &LoginController{})

	return router
}

/**
 * @description: 安全认证扫描
 * @param {*gin.Context} ctx
 * @return {*}
 */
func SecureHeader(ctx *gin.Context) {
	// 解决安全漏洞：检测到目标服务器启用了OPTIONS方法
	header := ctx.Writer.Header()
	header.Set("Access-Control-Allow-Origin", "*")
	// Access-Control-Allow-Credentials跨域问题
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE")
	header.Set("Access-Control-Max-Age", "86400")
	header.Set("Access-Control-Allow-Headers", "*")

	// 点击劫持：X-Frame-Options未配置
	header.Add("X-Frame-Options", "SAMEORIGIN")
	// 检测到目标Referrer-Policy响应头缺失
	header.Add("Referer-Policy", "origin")
	// 检测到目标Referrer-Policy响应头缺失
	header.Add("Referrer-Policy", "origin")
	// Content-Security-Policy响应头确实
	header.Add("Content-Security-Policy", "object-src 'self'")
	// 检测到目标X-Permitted-Cross-Domain-Policies响应头缺失
	header.Add("X-Permitted-Cross-Domain-Policies", "master-only")
	// 检测到目标X-Content-Type-Options响应头缺失
	header.Add("X-Content-Type-Options", "nosniff")
	// 检测到目标X-XSS-Protection响应头缺失
	header.Add("X-XSS-Protection", "1; mode=block")
	// 检测到目标X-Download-Options响应头缺失
	header.Add("X-Download-Options", "noopen")
	// 点击劫持：X-Frame-Options未配置
	header.Add("X-Frame-Options", "SAMEORIGIN")
	// HTTP Strict-Transport-Security缺失
	header.Add("Strict-Transport-Security", "max-age=63072000; includeSubdomains; preload")
	ctx.Next()
}

func UseCache(ctx *gin.Context) {
	path := ctx.Request.URL.Path
	if strings.HasSuffix(path, ".png") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") {
		ctx.Writer.Header().Set("Cache-Control", "public, max-age=86400")
	}
	ctx.Next()
}

func TailFile(c *gin.Context) {
	vals, _ := url.ParseQuery(c.Request.URL.RawQuery)
	prefix := ""
	day := ""
	size := 50

	val := vals["prefix"]
	if len(val) > 0 {
		prefix = val[0]
	}
	val = vals["day"]
	if len(val) > 0 {
		day = val[0]
		if day == "now" {
			day = easy.FormatNow(easy.YMD)
		}
	}

	val = vals["size"]
	if len(val) > 0 {
		num, err := strconv.Atoi(val[0])
		if err == nil {
			size = min(max(num, 10), 1000)
		}
	}

	c.Writer.Header().Add("Content-Type", "text/plain; charset=utf-8")
	path := fmt.Sprintf("%s%s", prefix, day)
	lines, err := tailsnap.TailFile(path, size)
	if err != nil {
		c.Writer.WriteString(err.Error())
		return
	}
	for _, line := range lines {
		if line != nil {
			c.Writer.WriteString(*line)
			c.Writer.WriteString("\n")
		}
	}
}

func TailLog(c *gin.Context) {
	vals, _ := url.ParseQuery(c.Request.URL.RawQuery)
	prefix := ""
	day := ""
	code := ""
	size := 50

	val := vals["prefix"]
	if len(val) > 0 {
		prefix = val[0]
	}
	val = vals["day"]
	if len(val) > 0 {
		day = val[0]
		if day == "now" {
			day = easy.FormatNow(easy.YMD)
		}
	}
	val = vals["code"]
	if len(val) > 0 {
		code = val[0]
	}

	val = vals["size"]
	if len(val) > 0 {
		num, err := strconv.Atoi(val[0])
		if err == nil {
			if num < 10 {
				size = 10
			} else if num > 1000 {
				size = 1000
			} else {
				size = num
			}
		}
	}

	c.Writer.Header().Add("Content-Type", "text/html; charset=utf-8")
	path := fmt.Sprintf("%s%s.log", prefix, day)
	lines, err := tailsnap.TailFile(path, size)
	if err != nil {
		c.Writer.WriteString(err.Error())
		fmt.Fprintln(c.Writer, "</br>", `
		<script>
			function onClose() {
				var iframe = parent.document.getElementById('customdlg');
				iframe.style.height = "0px";
				iframe.style.width = "0px";
				iframe.src=""
			 }
		 </script>
	 <input type="button" value="关闭窗口" onclick="onClose()">`)
		return
	}
	fmt.Fprintln(c.Writer, "<pre>")
	for _, line := range lines {
		if line != nil {
			if code == "GBK" {
				str, _ := simplifiedchinese.GBK.NewEncoder().String(*line)
				c.Writer.WriteString(str)
			} else {
				c.Writer.WriteString(*line)
			}
			c.Writer.WriteString("\r\n")
		}
	}
	fmt.Fprintln(c.Writer, "</pre>")
	fmt.Fprintln(c.Writer, `
				<script>
					function onClose() {
            			var iframe = parent.document.getElementById('customdlg');
            			iframe.style.height = "0px";
            			iframe.style.width = "0px";
						iframe.src=""
			 		}
					function onReload() {
                        parent.document.getElementById('customdlg').contentWindow.location.reload(true);
                	}
			 	</script>
			 <input type="button" value="关闭窗口" onclick="onClose()">
			 <input type="button" value="刷新" onclick="onReload()">`)
}

// 与TailLog 区别是否是Iframe
func Taillog(c *gin.Context) {
	vals, _ := url.ParseQuery(c.Request.URL.RawQuery)
	prefix := ""
	day := ""
	code := ""
	size := 50

	val := vals["prefix"]
	if len(val) > 0 {
		prefix = val[0]
	}
	val = vals["day"]
	if len(val) > 0 {
		day = val[0]
		if day == "now" {
			day = easy.FormatNow(easy.YMD)
		}
	}
	val = vals["code"]
	if len(val) > 0 {
		code = val[0]
	}

	val = vals["size"]
	if len(val) > 0 {
		num, err := strconv.Atoi(val[0])
		if err == nil {
			if num < 10 {
				size = 10
			} else if num > 1000 {
				size = 1000
			} else {
				size = num
			}
		}
	}

	path := fmt.Sprintf("%s%s.log", prefix, day)
	lines, err := tailsnap.TailFile(path, size)
	if err != nil {
		c.Writer.WriteString(err.Error())
		return
	}
	c.Writer.Header().Add("Content-Type", "text/html")
	c.Writer.WriteString(`<!DOCTYPE html>
		<html> <head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>查看日志</title>
		</head><body>`)

	fmt.Fprintln(c.Writer, "<pre>")
	for _, line := range lines {
		if line != nil {
			if code == "GBK" {
				str, _ := simplifiedchinese.GBK.NewEncoder().String(*line)
				c.Writer.WriteString(str)
			} else {
				c.Writer.WriteString(*line)
			}
			c.Writer.WriteString("\r\n")
		}
	}
	c.Writer.WriteString("</pre>")
	c.Writer.WriteString(`<script>
		function onReload() {location.reload(true);}
	 </script>
 <input type="button" value="刷新" onclick="onReload()">`)
	c.Writer.WriteString("</body></html>")
}
